#include "system/socket/Socket.hpp"
#include "system/socket/Connection.hpp"
#include "system/socket/SocketExceptions.hpp"
#include "system/socket/AbstractConnectionProcessor.hpp"
#include <string.h>
#include <fcntl.h>

namespace SystemSocket
{
	Socket::~Socket()
	{
		closeSocket();
	}

    void Socket::openSocket()
    {

    	struct protoent * aTcp = getprotobyname("tcp");
    	in_addr_t ip_addr = inet_addr("127.0.0.1");
    	struct sockaddr_in aBoundConnection;

    	this->_sockfd = socket(AF_INET, SOCK_STREAM, aTcp->p_proto);

    	// if we the socket couldn't be open then fail
    	if(this->_sockfd < 0)
    		throw SocketCreationException();

    	aBoundConnection.sin_family      = AF_INET;
    	aBoundConnection.sin_addr.s_addr = ip_addr;
    	aBoundConnection.sin_port        = htons(this->_portNumber);

    	// try to bind or fail
    	if(bind(this->_sockfd, (struct sockaddr *)&aBoundConnection, sizeof (struct sockaddr)) < 0)
    		throw SocketBindException();
    }


    void Socket::listen()
    {
        if(::listen(this->_sockfd, this->_backlog) == -1)
        {
            // another exception to be thrown
            throw ListenException();
        }
    }
    Connection * Socket::accept()
    {
    	unsigned int aActiveSockfd;
        struct sockaddr_storage their_addr;
        socklen_t addr_size = sizeof(their_addr);
        char s[INET6_ADDRSTRLEN];

        aActiveSockfd = ::accept(this->_sockfd, (struct sockaddr*)&their_addr, &addr_size);
        if(aActiveSockfd == -1)
        {
            return NULL;
        }
        {
        	inet_ntop(their_addr.ss_family, getInAddr((struct sockaddr *)&their_addr), s, sizeof s);
        	// create a new connection object and return for further processing
        	Connection * aNewConnection(new Connection(aActiveSockfd));
        	return aNewConnection;
        }
    }

    void Socket::setNonBlocking()
    {
    	if(fcntl(this->_sockfd, F_SETFL, O_NDELAY) < 0)
    		throw NonBlockingException();
    }


    void Socket::closeSocket()
    {
    	if(this->_sockfd != -1)
    	{
    		::close(this->_sockfd);
    		this->_sockfd = -1;
    	}
    }
}
